# coding=utf-8
import logging
from datetime import datetime, timedelta

from common import orm
from common.bankcard import db as bankcard_db
from common.card_statistics_daily import db as card_statistics_daily_db
from common.card_statistics_weekly import db as card_statistics_weekly_db
from common.order_withdraw.model import IssuingBank
from common.order_withdraw.model import OrderWithdraw, OrderWithdrawReassign
from common.utils import exceptions as err
from common.utils import id_generator
from common.utils.db import list_object, get, upsert, generate_filter
from common.utils.decorator import sql_wrapper
from common.utils.types import Enum
from sqlalchemy import func

ORDER_STATUS = Enum({
    "PENDING": (0L, u""),
    "PROCESSING": (1L, u""),
    "SUCCESS": (2L, u""),
    "MANUALSUCCESS": (3L, u""),
    "INVALID": (4L, u""),
    "PAYING": (5L, u""),
})

NOTIFY_STATUS = Enum({
    "READY": (0L, "ready to notify"),
    "FAILED": (1L, "notify failed"),
    "SUCCESS": (2L, "notify successed"),
})

_LOGGER = logging.getLogger('hydra')

__CUT_OFF_IN_MIN = 60
__AUTO_ASSIGN_NEW_CARD_TIME_IN_MIN = 5


@sql_wrapper
def get_order(order_id):
    return get(OrderWithdraw, order_id)


@sql_wrapper
def list_order(query_dct, order_type=None):
    return list_object(query_dct, OrderWithdraw, order_type)


@sql_wrapper
def get_order_overview(parsed_dct, order_type):
    query = orm.session.query(func.sum(OrderWithdraw.amount)).filter(OrderWithdraw.order_type == order_type)
    query = query.filter(generate_filter(parsed_dct, OrderWithdraw))
    exact_amount = query.filter(~OrderWithdraw.status.in_(
        (ORDER_STATUS.PENDING, ORDER_STATUS.PROCESSING,
         ORDER_STATUS.INVALID))).scalar()

    query1 = orm.session.query(func.sum(OrderWithdraw.amount)).filter(OrderWithdraw.order_type == order_type)
    amount = query1.filter(generate_filter(parsed_dct, OrderWithdraw)).scalar()

    success_rate1 = orm.session.query(func.count(OrderWithdraw)).filter(generate_filter(parsed_dct, OrderWithdraw)) \
        .filter(OrderWithdraw.status == ORDER_STATUS.MANUALSUCCESS).all()

    success_rate2 = orm.session.query(func.count(OrderWithdraw)).filter(generate_filter(parsed_dct, OrderWithdraw)) \
        .filter(OrderWithdraw.status == ORDER_STATUS.SUCCESS).all()

    success_rate = success_rate1[0][0] + success_rate2[0][0]
    success_rate = success_rate + 0.00

    return exact_amount if exact_amount else 0, amount if amount else 0, success_rate


@sql_wrapper
def mch_order_no_exist(mch_order_no):
    order_record = OrderWithdraw.query.filter(
        OrderWithdraw.mch_order_no == mch_order_no).first()
    return True if order_record else False


@sql_wrapper
def get_mch_order_no(mch_order_no):
    return OrderWithdraw.query.filter(
        OrderWithdraw.mch_order_no == mch_order_no).first()


@sql_wrapper
def order_no_exist(id):
    return True if get_order(id) else False


@sql_wrapper
def create_order(data, bankcard_name, bankcard_num, bankcard_bank,
                 bankcard_code):
    try:
        order = OrderWithdraw()
        order.id = id_generator.generate_long_id('hydra')
        order.mch_id = data['mch_id']
        order.mch_order_no = data['mch_order_no']
        order.player_id = data['player_id']
        # Source Bank Card
        order.source_account_name = bankcard_name
        order.source_account_number = bankcard_num
        order.source_bank = bankcard_bank
        order.source_card_code = bankcard_code
        # Destination Bank Card
        order.account_name = data['account_name']
        order.account_number = data['account_number']
        order.bank = data['bank']
        order.amount = data['amount']
        order.notify_url = data['notify_url']
        order.mch_create_ip = data['mch_create_ip']
        order.sign = data['sign']
        order.status = ORDER_STATUS.PROCESSING
        order.save()
        card_statistics_daily_db.upsert_card_statistics(
            order.source_card_code, order.amount)
        card_statistics_weekly_db.upsert_card_statistics(
            order.source_card_code, order.amount)
        return order
    except Exception as e:
        _LOGGER.exception('create_order_withdraw error, %s', e)
        raise err.DataError()


@sql_wrapper
def create_buffer_order(hydra_name, first_card, second_card, amount):
    try:
        order = OrderWithdraw()
        order.id = id_generator.generate_long_id(hydra_name)
        order.mch_id = first_card['mch_id']
        order.mch_order_no = id_generator.generate_long_id(hydra_name)
        order.player_id = '-'
        # Source Bank Card
        order.source_account_name = first_card['account_holder']
        order.source_account_number = first_card['account_number'].replace(' ', '')
        order.source_bank = first_card['bank']
        order.source_card_code = first_card['name']
        # Destination Bank Card
        order.account_name = second_card['account_holder']
        order.account_number = second_card['account_number'].replace(' ', '')
        order.bank = second_card['bank']
        order.amount = amount
        order.notify_url = '-'
        order.mch_create_ip = '-'
        order.sign = '-'
        order.status = ORDER_STATUS.PROCESSING
        if hydra_name == 'hydra_recharge':
            order.order_type = 3
        else:
            order.order_type = 5
        order.save()
        card_statistics_daily_db.upsert_card_statistics(
            order.source_card_code, order.amount)
        card_statistics_weekly_db.upsert_card_statistics(
            order.source_card_code, order.amount)
        return order
    except Exception as e:
        _LOGGER.exception('create_order_withdraw error, %s', e)
        raise err.DataError()


@sql_wrapper
def edit_order(data):
    try:
        order_id = data['order_id']
        order = get_order(order_id)
        order.status = int(data['status'])
        order.notes = data['notes']
        order.updated_by = data['updated_by']
        operating_time = data['operating_time']
        if not operating_time:
            operating_time = datetime.utcnow()
        order.operating_time = operating_time
        order.exact_time = datetime.utcnow()
        order.save()
        card_statistics_daily_db.upsert_card_statistics(
            order.source_card_code, order.amount, order.status)
        return order
    except Exception as e:
        _LOGGER.exception('edit_order error, %s', e)
        raise err.DataError()


@sql_wrapper
def push_order(id, status):
    try:
        order = get_order(id)
        if order:
            order.notify_status = status
            order.notify_count += 1
            order.notified_at = datetime.utcnow()
            order.save()

    except Exception as e:
        _LOGGER.exception('push_order error, %s', e)
        raise err.DataError()


@sql_wrapper
def upsert_order_withdraw(info, id=None, update_statistics=False):
    # if update_statistics, do not update amount not necessary
    amount = None
    if update_statistics:
        amount = info['amount']
        info.pop('amount')

    result = upsert(OrderWithdraw, info, id)

    if update_statistics:
        card_statistics_daily_db.upsert_card_statistics(
            info["source_card_code"], amount, order_status=-1, reverse=True)
        card_statistics_daily_db.upsert_card_statistics(
            info["source_card_code"], amount)

    return result


@sql_wrapper
def get_auto_invalid_orders():
    orders = [
        o for o in OrderWithdraw.query.filter(~OrderWithdraw.status.in_(
            (ORDER_STATUS.SUCCESS, ORDER_STATUS.MANUALSUCCESS,
             ORDER_STATUS.INVALID, ORDER_STATUS.PAYING))).all()
        if datetime.now() >= o.created_at + timedelta(minutes=__CUT_OFF_IN_MIN)
    ]
    return orders


@sql_wrapper
def get_notified_failed_orders():
    orders = [
        o for o in OrderWithdraw.query.filter(
            OrderWithdraw.status.in_((ORDER_STATUS.SUCCESS,
                                      ORDER_STATUS.MANUALSUCCESS,
                                      ORDER_STATUS.INVALID))).all()
        if datetime.now() >= o.created_at + timedelta(minutes=__CUT_OFF_IN_MIN)
        and o.notify_status in [NOTIFY_STATUS.READY, NOTIFY_STATUS.FAILED]
    ]
    return orders


@sql_wrapper
def get_auto_assign_new_card_orders():
    orders = OrderWithdraw.query.filter(
        (OrderWithdraw.created_at
         if not OrderWithdraw.reassign_time else OrderWithdraw.reassign_time) +
        timedelta(minutes=__AUTO_ASSIGN_NEW_CARD_TIME_IN_MIN)
        >= datetime.utcnow(),
        OrderWithdraw.status.in_([ORDER_STATUS.PROCESSING
                                  ]), OrderWithdraw.exe_locked == False,
        OrderWithdraw.show_ukey == False).all()

    return orders


@sql_wrapper
def get_orders_for_exe(card_id):
    bankcard = bankcard_db.get_bankcard(card_id)

    if not bankcard:
        raise err.ResourceNotFound(
            'card_id {} cannot be not found'.format(card_id))

    orders = OrderWithdraw.query.filter(
        ~OrderWithdraw.status.in_([2, 3, 4, 5]),
        OrderWithdraw.source_bank == bankcard.bank,
        OrderWithdraw.source_card_code == bankcard.name,
        OrderWithdraw.show_ukey == False,
    ).order_by(OrderWithdraw.created_at).all()

    order_items = []
    issuingBanks = IssuingBank.query.filter(
        IssuingBank.source_bank == bankcard.bank_en
        and IssuingBank.active == 1)
    for item in orders:
        data = item.as_dict()
        issuing_ = filter(lambda b: data["bank"] == b.destination_bank,
                          issuingBanks)
        data["issuing_bank"] = issuing_[0].name
        order_items.append(data)

    return order_items


@sql_wrapper
def create_order_withdraw_reassign(order_id, reassign_time, old_card_code,
                                   new_card_code):
    try:
        reassign = OrderWithdrawReassign()
        reassign.order_id = order_id
        reassign.assign_at = reassign_time
        reassign.card_code_from = old_card_code
        reassign.card_code_to = new_card_code
        reassign.save()
    except Exception as e:
        _LOGGER.exception('create_order_withdraw_reassign error, %s', e)
        raise err.DataError()
